{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Paso 2: Proveyendo modelos de manera segura con Syft Keras\n",
    "\n",
    "Ahora que has entrenado un modelo con Keras, estás listo para proveer predicciones privadas. Podemos hacer eso usando Syft Keras.\n",
    "\n",
    "Para asegurar y proveer este modelo, vamos a necesitar tres TFEWorkers (servidores). La razón es que TF Encrypted utiliza una técnica de encriptación llamada [Computación Multi-Parte (CMP, o MPC en inglés)](https://en.wikipedia.org/wiki/Secure_multi-party_computation). La idea es dividir los pesos del modelo y los datos de entrada en \"acciones\", para luego enviar una acción de cada valor a los servidores diferentes. La propiedad clave es que si observas a la acción de un servidor, no se revela nada sobre el valor original (sea de los datos de entrada o los pesos del modelo).\n",
    "\n",
    "Vamos a definir un modelo Syft Keras como hicimos en el notebook anterior. Sin embargo, hay un truco: antes de instanciar este modelo, correremos `hook = sy.KerasHook(tf.keras)`. Esto agregará tres nuevos métodos importantes a la clase de Keras Sequential: \n",
    "\n",
    " - `share`: Esto asegurará tu modelo usando 'secret sharing' (convirtiendo el modelo en un \"secreto\" para luego ser dividido); por defecto, usará el protocolo SecureNN de TF Encrupted para compartir el secreto entre los tres TFEWorkers. Esto agregará la capacidad de proveer predicciones en datos encriptados\n",
    "\n",
    " - `serve`: Esta función empezará una cola de provisiones, donde los TFEWorkers pueden aceptar pedidos de predicciones. Estas predicciones vendrán de un modelo que está protegido del exterior. \n",
    "\n",
    " - `shutdown_workers`: Una vez hayas terminado de proveer predicciones, puedes apagar tu modelo usando esta función. Te redirigirá a apagar los procesos del servidor manualmente si has optado por manejar cada trabajador manualmente. \n",
    " \n",
    "Si quieres aprender más sobre CMP, visita este excelente [blog](https://mortendahl.github.io/2017/04/17/private-deep-learning-with-mpc/) (en inglés)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "from tensorflow.keras import Sequential\n",
    "from tensorflow.keras.layers import AveragePooling2D, Conv2D, Dense, Activation, Flatten, ReLU, Activation\n",
    "\n",
    "import syft as sy\n",
    "hook = sy.KerasHook(tf.keras)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Modelo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Como puedes ver, definimos casi el mismo modelo que antes, excepto que esta vez proveemos un `batch_input_shape` (tamaño del batch de los datos de entrada). Esto permite a TF Encrypted optimizar las computaciones seguras a través de tamaños predeterminados de tensores. Para este demo con MNIST, enviaremos datos de entrada con un tamaño de (1, 28, 28, 1). \n",
    "También obtendremos el logit en vez del resultado de softmax porque esta operación es complicada de hacer usando CMP, y no la necesitamos para responder a los pedidos de predicciones. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "num_classes = 10\n",
    "input_shape = (1, 28, 28, 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = Sequential()\n",
    "\n",
    "model.add(Conv2D(10, (3, 3), batch_input_shape=input_shape))\n",
    "model.add(AveragePooling2D((2, 2)))\n",
    "model.add(Activation('relu'))\n",
    "model.add(Conv2D(32, (3, 3)))\n",
    "model.add(AveragePooling2D((2, 2)))\n",
    "model.add(Activation('relu'))\n",
    "model.add(Conv2D(64, (3, 3)))\n",
    "model.add(AveragePooling2D((2, 2)))\n",
    "model.add(Activation('relu'))\n",
    "model.add(Flatten())\n",
    "model.add(Dense(num_classes, name=\"logit\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cargando pesos pre-entrenados"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con `load_weights` puedes cargar los pesos que has guardado anteriormente después de entrenar tu modelo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pre_trained_weights = 'short-conv-mnist.h5'\n",
    "model.load_weights(pre_trained_weights)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Usando los trabajadores"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora crearemos TFEWorkers (`alice`, `bob` y `carol`) requeridos por TF Encrypted para calcular predicciones privadas. Por cada TFEWorker, necesitas especificar un host. Luego, combinamos estos trabajadores en un clúster. \n",
    "\n",
    "Estos trabajadores corren en un [servidor de TensorFlow](https://www.tensorflow.org/api_docs/python/tf/distribute/Server), el cual puedes manejar manualmente (`AUTO = False`) o pedir a los trabajadores que lo manejen por ti (`AUTO = True`). Si escoges manejarlos manualmente, tendrás que ejecutar un comando en el terminal en cada dispositivo de host por cada trabajador después de llamar `cluster.start()` abajo. Si es que todos los trabajadores están bajo el mismo host en un solo dispositivo (e.g `localhost`), puedes escoger que Syft maneje el servidor Tensorflow de los trabajadores."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "AUTO = False\n",
    "\n",
    "alice = sy.TFEWorker(host='localhost:4000', auto_managed=AUTO)\n",
    "bob = sy.TFEWorker(host='localhost:4001', auto_managed=AUTO)\n",
    "carol = sy.TFEWorker(host='localhost:4002', auto_managed=AUTO)\n",
    "\n",
    "cluster = sy.TFECluster(alice, bob, carol)\n",
    "cluster.start()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Asegurando el modelo, calculando los pesos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Gracias a `sy.KerasHook(tf.keras)`, puedes llamar al método `share` para transformar tu modelo en un modelo de TF Encrypted.\n",
    "\n",
    "Si has decidido manejar los servidores manualmente en el código de arriba, entonces este paso no completará hasta que todos los servidores hayan empezado a correr. Puede que tu firewall te pida permiso para que Python acepte la conexión."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.share(cluster)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Proveyendo el modelo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Perfecto! Ahora al llamar `model.serve`, tu modelo está listo para proveer predicciones privadas. Puedes ajustar `num_requests` para delimitar el número de predicciones que el modelo podrá proveer; Si no está especificado, entonces el modelo proveerá predicciones hasta que sea interrumpido."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.serve(num_requests=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Estamos listos para proseguir al notebook **Parte 13c** para pedir predicciones privadas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Limpieza!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una vez hayas topado el límite de pedidos, el modelo no podrá estar disponible para proveer predicciones, pero aun mantendrá el secreto entre los tres trabajadores. Puedes apagar a los trabajadores ejecutando el código siguiente. \n",
    "\n",
    "**Felicidades** por terminar la parte 13b: Clasificación Segura con Syft Keras y TFE!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.stop()\n",
    "cluster.stop()\n",
    "\n",
    "if not AUTO:\n",
    "    process_ids = !ps aux | grep '[p]ython -m tf_encrypted.player --config' | awk '{print $2}'\n",
    "    for process_id in process_ids:\n",
    "        !kill {process_id}\n",
    "        print(\"Process ID {id} has been killed.\".format(id=process_id))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
